/*
  Time-stamp: <2009-11-14 20:41:58 xinhaoyuan>

  File: sched.c

  Provide simple routine for task scheduling and monitoring. And also
  implement a simple scheduler.
  
*/

#define _EKOS_KERNEL_C_
#include <kernel/kernel.h>

void
refresh_envs_monitor(void)
{
	 static char k;
	 int s = "-/|\\"[k = (k + 1) % 4];
	 vga_put_sc(0, s);
	 int i;
	 for (i = 0; i != 16; ++i)
	 {
		  if (envs[i].env_status == ENV_INUSE)
		  {
			   switch (envs[i].sch_status) {
			   case ENV_SCH_RUNNING:
					s = 'R';
					break;
			   case ENV_SCH_SLEEPING:
					s = 'S';
					break;
			   case ENV_SCH_WAITING:
					s = 'w';
					break;
			   case ENV_SCH_IEC_RECV_BLOCKING:
					s = 'r';
					break;
			   case ENV_SCH_IEC_SEND_BLOCKING:
					s = 's';
					break;
			   default:
					s = '?';
			   }
			   
			   if (envs[i].inkern)
					s |= 0x4700;
		  }
		  else
		  {
			   s = ' ';
		  }

		  /* change the background color if the env is in kernel mode */
		  
			   
		  vga_put_sc(i + 1, s);
	 }
}

int cur_env;
uint8_t envs_switch_lock;

static struct scheduler_t current_scheduler;

KERNEL_EXPORT void
sch_yield(uint16_t status)
{
     envs[cur_env].sch_status = status;
	 call_yield();
}

KERNEL_EXPORT void
sl_lock(volatile uint8_t *lock)
{
	 while (!sl_trylock(lock))
	 	  sch_yield(ENV_SCH_WAITING);
}

KERNEL_EXPORT int
sch_sleep(uint16_t head, volatile uint8_t *lock)
{
	 int result = current_scheduler.sleep(current_scheduler.data, head);
	 if (lock != NULL) sl_release(lock);
	 return result;
}

KERNEL_EXPORT int
sch_wakeup(uint16_t idx, volatile uint8_t *lock)
{
	 int result = current_scheduler.wakeup(current_scheduler.data, idx);
	 if (lock != NULL) sl_release(lock);
	 return result;
}

KERNEL_EXPORT int
sch_attach(uint16_t ori, uint16_t idx)
{
	 sl_lock(&envs_switch_lock);
	 int result = current_scheduler.attach(current_scheduler.data, ori, idx);
     sl_release(&envs_switch_lock);
	 return result;
}

KERNEL_EXPORT int
sch_detach(void)
{
	 return current_scheduler.detach(current_scheduler.data);
}

KERNEL_EXPORT void
scheduler(uint32_t *esp, int hint)
{
	 if (sl_trylock(&envs_switch_lock))
	 {
		  int next = current_scheduler.get_sched_next(current_scheduler.data, hint);
		  
		  if (next < 0) {
			   /* WORKING: SWITCHING ERROR */
		  }
		  else
		  {
			   /* restore temp stack pointer */
			   envs[cur_env].ssp = *esp;
			   envs[cur_env].ksp = task.esp0;

			   /* set new env */
			   envs[next].sch_status = ENV_SCH_RUNNING;

			   /* set the stack pointer */
			   *esp = envs[next].ssp;
			   task.esp0 = envs[next].ksp;
	       
			   /* Set the new cr3 */
			   dyninfo.si_cr3 = envs[next].cr3;

			   cur_env = next;

			   /* monitor! */
			   refresh_envs_monitor();
		  }

		  sl_release(&envs_switch_lock);
	 }
}

/* ============================================================ */

/* 进入睡眠状态, 并解锁 */
static int
simple_sleep(void *data, uint16_t head)
{
	 if (cur_env != head && envs[head].sch_status != ENV_SCH_SLEEPING)
		  return -E_BAD_ENV;

	 /* 防止最后的进程被消灭 =.= */
	 if (envs[cur_env].sched_info.next == cur_env)
	 {
		  EFLAGS_T f = intr_disable_begin();
		  cprintf("[sleep] %d is the only environment, can't sleep!\n", cur_env, head);
		  intr_disable_end(f);
		  
		  return -E_BAD_ENV;
	 }

	 sl_lock(&envs_switch_lock);

	 envs[cur_env].sch_status = ENV_SCH_SLEEPING;

	 envs[cur_env].sched_info.sleep_rep =
		  envs[cur_env].sched_info.prev;	 
	 
	 envs[envs[cur_env].sched_info.next].sched_info.prev =
		  envs[cur_env].sched_info.prev;
	 envs[envs[cur_env].sched_info.prev].sched_info.next =
		  envs[cur_env].sched_info.next;

	 if (head != cur_env)
	 {
		  envs[envs[head].sched_info.prev].sched_info.next
			   = cur_env;
		  envs[cur_env].sched_info.prev = envs[head].sched_info.prev;
	 }
		  
	 envs[head].sched_info.prev = cur_env;
	 envs[cur_env].sched_info.next = head;

#if 0
	 NOINTR_S;
	 cprintf("[sleep] %d sleep at tail of %d\n", cur_env, head);	 
	 NOINTR_E;
#endif

	 sl_release(&envs_switch_lock);
	 return 0;
}

/* 唤醒进程 */
static int
simple_wakeup(void *data, uint16_t idx)
{
	 if (envs[idx].sch_status != ENV_SCH_SLEEPING)
		  return -E_BAD_ENV;

	 sl_lock(&envs_switch_lock);

	 /* 删除后环为空则跳过 */
	 if (envs[idx].sched_info.next != idx)
	 {
		  envs[envs[idx].sched_info.next].sched_info.prev =
			   envs[idx].sched_info.prev;
		  envs[envs[idx].sched_info.prev].sched_info.next =
			   envs[idx].sched_info.next;
	 }

	 envs[envs[cur_env].sched_info.prev].sched_info.next
		  = idx;
	 envs[idx].sched_info.prev = envs[cur_env].sched_info.prev;
	 
	 envs[cur_env].sched_info.prev = idx;
	 envs[idx].sched_info.next = cur_env;

	 envs[idx].sch_status = ENV_SCH_WAITING;

#if 0
	 /* NOINTR_S; */
	 /* cprintf("[wakeup] from %d, %d wake up\n", cur_env, idx); */
	 /* NOINTR_E; */
#endif

	 sl_release(&envs_switch_lock);
	 return 0;
}

static int
simple_attach(void *data, uint16_t ori, uint16_t idx)
{
     envs[idx].sched_info.next = cur_env;
     envs[idx].sched_info.prev = envs[cur_env].sched_info.prev;
	 
     envs[cur_env].sched_info.prev = idx;
     envs[envs[idx].sched_info.prev].sched_info.next = idx;
	 
	 envs[idx].sch_status = ENV_SCH_WAITING;

	 return 0;
}

static int
simple_detach(void *data)
{
	 /* To preserve the last environment to be detached =.= */
	 if (envs[cur_env].sched_info.next == cur_env)
	 {
		  EFLAGS_T f = intr_disable_begin();
		  cprintf("[detach] %d is the only environment, can't detach!\n", cur_env);
		  intr_disable_end(f);
		  
		  return -E_BAD_ENV;
	 }

	 sl_lock(&envs_switch_lock);

	 envs[cur_env].sch_status = ENV_SCH_DETACHED;

	 envs[cur_env].sched_info.sleep_rep =
		  envs[cur_env].sched_info.prev;
	 
	 envs[envs[cur_env].sched_info.next].sched_info.prev =
		  envs[cur_env].sched_info.prev;
	 envs[envs[cur_env].sched_info.prev].sched_info.next =
		  envs[cur_env].sched_info.next;

	 envs[cur_env].sched_info.next = ENV_NULL;
	 envs[cur_env].sched_info.prev = ENV_NULL;
	 
#if 0
	 NOINTR_S;
	 cprintf("[detach] %d detached\n", cur_env);	 
	 NOINTR_E;
#endif

	 return 0;
}

static int
simple_get_sched_next(void *data, int hint)
{
	 uint16_t end;

	 if (envs[cur_env].sch_status == ENV_SCH_SLEEPING ||
		 envs[cur_env].sch_status == ENV_SCH_DETACHED)
	 {
		  end = envs[cur_env].sched_info.sleep_rep;
	 }
	 else end = cur_env;

	 if (envs[cur_env].sch_status == ENV_SCH_RUNNING)
		  envs[cur_env].sch_status = ENV_SCH_WAITING;

	 uint16_t n = end;

	 while (true)
	 {
	  
		  n = envs[n].sched_info.next;

		  /* Find the first switchable env */
		  /* 先处理 IEC */
		  iec_process(n);

		  if  (envs[n].sch_status != ENV_SCH_WAITING)
		  {
			   continue;
		  }

		  return n;
	 }
}

/* ============================================================ */

KERNEL_EXPORT int
sch_init(void)
{
	 sl_init(&envs_switch_lock);

	 current_scheduler.get_sched_next = &simple_get_sched_next;
	 current_scheduler.sleep		  = &simple_sleep;
	 current_scheduler.wakeup		  = &simple_wakeup;
	 current_scheduler.attach		  = &simple_attach;
	 current_scheduler.detach		  = &simple_detach;

     uint32_t i;
     for (i = 0; i != ENVS_MAXCOUNT; ++i)
     {
		  envs[i].sched_info.next = i + 1;
		  envs[i].sched_info.prev = i - 1;
		  
		  envs[i].sched_info.sleep_rep = ENV_NULL;
	 }

	 cur_env = 0;
	 
	 envs[0].sch_status = ENV_SCH_RUNNING;
     envs[0].sched_info.next =
		  envs[0].sched_info.prev = 0;
}
